import xml.etree.ElementTree as et
import copy
from pprint import pformat, isreadable

#import cmdobjects
import boqdoc

class DocProxy(object):
   '''A conversion from the boqdoc.Doc structure to string lists. The assumption 
   is that every object is either a CAttribute, a CAttributeBase or a simple 
   type for which obj == eval(repr(obj)) is true.
   '''
   
   def __init__(self, doc):
      self.d = doc
      self.objects = {}
      

   def isbase(self, obj):
      '''Return true is the object has attributes, i.e. is a CAttrBase.'''
      return isinstance(obj, cmdobjects.CAttrBase)
   
   def get_attrs(self):
      '''{owner_id: [(name, value, link)]}.
      If link is true, then value is an object id.'''
      ret = {}
      for obj in self.d.all_objects:
         if len(obj.attrs) > 0:
            ret[id(obj)] = [(n, repr(v), self.isbase(v)) for n, v in obj.attrs.todict().items()]
      return ret
         
   def set_attrs(self, attributes):
      '''Loads a list of attributes in the format returned by attrs.'''
      for owner, attrs in attributes.items():
         if str(owner) not in self.objects:
            error('attribute owner missing: %s' % owner)
            continue
            
         for name, value, link in attrs:
            if not link:
               setattr(self.objects[str(owner)], name, eval(value))
            elif value in self.objects:
               setattr(self.objects[str(owner)], name, self.objects[str(value)])
            else:
               error('attribute', name, value)
   
   def _get_section(self, section):
      return (id(section), [id(item) for item in section])
      
   def get_master(self):
      '''Return a list of sections and items in the master in the form:
      (<masterid>, {<section>: [<list of items>], <section>: [<list of items>]})
      '''
      return (id(self.d.master), dict([ self._get_section(sec) for sec in self.d.master.sections ]))

   def set_master(self, master):
      self.objects[str(master[0])] = self.d.master
      for section, items in master[1].items():
         sec = self.objects[str(section)] = self.d.master.sections.append()
         for item in items:
            self.objects[str(item)] = sec.append()
   
   def get_schedules(self):
      '''{<id>: {<section>: [<item list>]}}'''
      return dict([ (id(sch), 
         dict([self._get_section(sec) for sec in sch.sections])) for sch in self.d.schedules ])
         
   def set_schedules(self, schedules):
      for schid, sections in schedules.items():
         sch = self.objects[str(schid)] = self.d.schedules.append()
         for secid, items in sections.items():
            sec = sch.append(self.objects[str(secid)])
            for itemid in items:
               sec.append(self.objects[str(itemid)])

   def _get_column(self, col):
      '''Return a tuple (<id>, <column type>, <keys>, {<key: value>}) for a specific column.'''
      return ( id(col), col.__class__.__name__, id(col.cellattrs.linked), 
              dict([ (id(key), value) for key, value in col._data.iteritems() ]) )
              
   def _create_column(self, col, table):
      if str(col[2]) not in self.objects:
         error('column key missing: %s' % str(col[2]))
      else:
         ret = boqdoc.Column(col[1], keys=self.objects[str(col[2])])
         self.objects[str(col[0])] = ret
         table.columns.append(ret)
         for key, value in col[3]:
            ret._setitem(self.objects[str(key)], value)
         return ret
      
   def get_columns(self):
      # ([schedule columns], {schedule: ([section cols], [item cols])} )
      ret = [None, {}]
      ret[0] = [self._get_column(col) for col in self.d.scheduletable.columns]
      for sch in self.d.schedules:
         ret[1][id(sch)] = ([self._get_column(col) for col in sch.sectiontable.columns],
                            [self._get_column(col) for col in sch.itemtable.columns])
      return tuple(ret)
      
   def set_columns(self, columns):
      for col in columns[0]:
         self._create_column(col, self.d.scheduletable)
      for sch, collists in columns[1].items():
         for col in collists[0]:
            self._create_column(col, sch.sectiontable)
         for col in collists[1]:
            self._create_column(col, sch.itemtable)
         
   attributes = property(get_attrs, set_attrs)
   master = property(get_master, set_master)
   schedules = property(get_schedules, set_schedules)
   columns = property(get_columns, set_columns)
   
   
def error(msg):
   print msg
   
def save(doc, file):

   dp = DocProxy(doc)
   root = et.Element('soqdoc')
   
   eldoc = et.SubElement(root, 'doc')
   for name, attr in doc.attrs.iteritems():
      # Store repr(attribute) so that eval(text) can be used when unpacking.
      func = pformat if isreadable(attr.value) else repr
      et.SubElement(eldoc, name).text = '\n%s\n' % func(attr.value)

   for tag in ('attributes', 'master', 'schedules', 'columns'):
      func = pformat if isreadable(getattr(dp, tag)) else repr
      et.SubElement(root, tag).text = '\n%s\n' % func(getattr(dp, tag))

   return et.ElementTree(root).write(file)


def load(file, doc):

   dp = DocProxy(doc)
   root = et.ElementTree(file=file).getroot()
   
   for element in root.find('doc'):
      try:
         value = eval(element.text)
      except:
         error("error evaluating in section 'doc': %s" % element.text)
      else:
         setattr(doc, element.tag, value)
      
   # The order of these is important, as each one will add items used in the next
   for tag in ('master', 'schedules', 'columns', 'attributes'):
      text = root.find(tag).text
      try:
         value = eval(text)
      except:
         error("error evaluating in section '%s': %s" % (tag, text))
      else:
         setattr(dp, tag, value)
      
